package main

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"log"
	"log/slog"
	"net"
	"os"
	"os/signal"
	"protobuf-learn/internal/config"
	"protobuf-learn/internal/hello"
	"protobuf-learn/internal/interceptor"
	"protobuf-learn/internal/streaming"
	"protobuf-learn/internal/todo"
	"protobuf-learn/proto"
	"syscall"
	"time"

	"golang.org/x/sync/errgroup"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/reflection"
)

func main() {

	// 监听操作系统信号来创建一个可取消的 context.Context, 一旦收到指定的系统信号，返回的 context.Context 会被取消。这提供了一种更简洁和可控的方式来处理信号,特别是与 Go 的 context.Context 机制结合使用时，更适合控制多个 Goroutine 的生命周期。
	// os.Interrupt: 通常是 SIGINT, 即 Ctrl+C
	// os.Interrupt: Ctrl+C
	// syscall.SIGTERM 是 Unix/Linux 系统上常用的信号，表示请求进程正常退出。与 SIGKILL 不同的是，SIGTERM 可以被捕获和处理
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stop()
	if err := run(ctx); err != nil && !errors.Is(err, context.Canceled) {
		// log 和 slog 是 Go 语言中两个用于日志记录的库。log 是 Go 标准库中自带的日志包，而 slog 是在 Go 1.21 中引入的结构化日志包（slog 全名为 log/slog）。slog 提供了更灵活、现代的日志记录方式，特别是在处理结构化日志时更为方便。
		slog.Error("error running application", slog.String("error", err.Error()))
		os.Exit(1)
	}

	slog.Info("closing server gracefully")
}

func newGRPCServer() (*grpc.Server, error) {
	var serverOpts []grpc.ServerOption

	switch os.Getenv("TLS_MODE") {
	case "tls":
		log.Println("TLS used")
		// load server tls files
		tlsCredentials, err := credentials.NewServerTLSFromFile("certs/server.crt", "certs/server.key")
		if err != nil {
			return nil, fmt.Errorf("failed to lod tls credentials: %w", err)
		}
		serverOpts = append(serverOpts, grpc.Creds(tlsCredentials))
	case "mtls":
		log.Println("MTLS used")
		// load server tls files
		serverCert, err := tls.LoadX509KeyPair("certs/server.crt", "certs/server.key")
		if err != nil {
			return nil, fmt.Errorf("failed to load tls certs: %w", err)
		}
		caContent, err := os.ReadFile("certs/ca.crt")
		if err != nil {
			return nil, fmt.Errorf("failed to open CA cert: %w", err)
		}
		certPool := x509.NewCertPool()
		if !certPool.AppendCertsFromPEM(caContent) {
			return nil, fmt.Errorf("failed to append CA cert to pool")
		}

		tlsCredentials := credentials.NewTLS(&tls.Config{
			Certificates: []tls.Certificate{serverCert},
			ClientCAs:    certPool,
			ClientAuth:   tls.RequireAndVerifyClientCert,
		})
		serverOpts = append(serverOpts, grpc.Creds(tlsCredentials))

	}

	serverOpts = append(serverOpts,
		// 服务端获取普通请求的中间件
		grpc.ChainUnaryInterceptor(
			func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
				start := time.Now()
				resp, err = handler(ctx, req)
				duration := time.Since(start)
				log.Printf("response %s took %s", info.FullMethod, duration)
				return resp, err
			},
			func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
				log.Printf("request received on server: %s", info.FullMethod)
				resp, err = handler(ctx, req)
				log.Printf("sending response: %s", info.FullMethod)
				return resp, err
			},
		),
		// 服务端获取流接口的中间件
		grpc.ChainStreamInterceptor(
			func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
				start := time.Now()
				err := handler(srv, ss)
				duration := time.Since(start)
				log.Printf("stream response %s took %s", info.FullMethod, duration)
				return err
			},
			func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
				log.Printf("stream request received on server: %s", info.FullMethod)
				err := handler(srv, ss)
				log.Printf("sending stream response: %s", info.FullMethod)
				return err
			},
		),
	)

	return grpc.NewServer(serverOpts...), nil
}

func run(ctx context.Context) error {

	port, ok := os.LookupEnv("PORT")
	if !ok {
		log.Fatal("PORT env var missing")
	}

	grpcServer, err := newGRPCServer()
	if err != nil {
		log.Fatal(err)
	}
	// enable reflection on server (not for production use!)
	reflection.Register(grpcServer)

	host := fmt.Sprintf(":%s", port)

	helloService := hello.Service{}
	todoService := todo.NewService()
	streamingService := streaming.NewService()
	configService := config.NewService(host)

	proto.RegisterHelloServiceServer(grpcServer, helloService)
	proto.RegisterTodoServiceServer(grpcServer, todoService)
	proto.RegisterStreamingServiceServer(grpcServer, streamingService)
	proto.RegisterFileServiceServer(grpcServer, &streaming.FileService{})
	proto.RegisterInterceptorServiceServer(grpcServer, &interceptor.Service{})
	proto.RegisterConfigServiceServer(grpcServer, configService)

	/**
	errgroup 是 Go 标准库 golang.org/x/sync/errgroup 中的一个包，它帮助处理多个 Goroutine 的并发任务，同时简化了错误处理流程。如果在并发执行的 Goroutine 中有任何一个任务返回了错误，errgroup 会捕获该错误并立即取消剩余的任务。
	它通常用于需要并发执行多个任务，并且希望在其中任何一个失败时停止所有任务，并获取最先发生的错误。
	*/
	//errgroup.WithContext: 创建一个 errgroup.Group，并返回一个 Context，该 Context 在任何 Goroutine 返回错误时会被取消。
	grp, ctx := errgroup.WithContext(ctx)

	//group.Go(func() error): 启动一个新的 Goroutine，并在其中执行任务。函数返回错误时，errgroup 会捕获这个错误。
	grp.Go(func() error {
		lis, err := net.Listen("tcp", host)
		if err != nil {
			return fmt.Errorf("faild to listen on address: %q: %w", host, err)
		}

		slog.Info("starting grpc server on address", slog.String("address", host))

		if err := grpcServer.Serve(lis); err != nil {
			return fmt.Errorf("failed to serve grpc service: %w", err)
		}

		return nil
	})

	grp.Go(func() error {
		<-ctx.Done()

		grpcServer.GracefulStop()

		return nil
	})

	//等待所有 Goroutine 完成。如果其中任何一个返回错误，Wait() 会返回该错误
	return grp.Wait()
}
